use chrono::{DateTime, NaiveDate, Utc};
use oxidiviner::{quick, TimeSeriesData};
use serde_json::Value;
use shq_common::prelude::report::TimeType;
pub struct ForecastService {
    datas: TimeSeriesData,
    periods: usize,
}
impl ForecastService {
    pub fn new(
        timestamps: Vec<DateTime<Utc>>,
        values: Vec<f64>,
        name: &str,
        periods: usize,
    ) -> anyhow::Result<Self> {
        let datas = TimeSeriesData::new(timestamps, values, name)?;
        Ok(Self { datas, periods })
    }
    pub fn from(
        time_type: TimeType,
        times: Vec<Value>,
        values: Vec<Value>,
        name: &str,
        periods: usize,
    ) -> anyhow::Result<Self> {
        let timestamps = to_timestamp_vec(time_type, times)?;
        let values = to_value_vec(values)?;
        let datas = TimeSeriesData::new(timestamps, values, name)?;
        Ok(Self { datas, periods })
    }
}
impl ForecastService {
    pub fn auto_select(self) -> anyhow::Result<(Vec<f64>, String)> {
        let (forecast, model_used) = quick::auto_select(self.datas, self.periods)?;
        Ok((forecast, model_used))
    }
    pub fn moving_average(self) -> anyhow::Result<Vec<f64>> {
        let res = quick::moving_average(self.datas, self.periods, None)?;
        Ok(res)
    }
    pub fn arima(self) -> anyhow::Result<Vec<f64>> {
        let res = quick::arima(self.datas, self.periods)?;
        Ok(res)
    }
    pub fn exponential_smoothing(self) -> anyhow::Result<Vec<f64>> {
        let res = quick::exponential_smoothing(self.datas, self.periods, None)?;
        Ok(res)
    }
}
fn to_timestamp(time_type: TimeType, value: Value) -> anyhow::Result<DateTime<Utc>> {
    match value {
        Value::Number(num) => {
            if num.is_i64() {
                let ts = num.as_i64().unwrap();
                let (year, month, day) = match time_type {
                    TimeType::Day => {
                        let year = (ts / 10000) as i32;
                        let month = ((ts % 10000) / 100) as u32;
                        let day = (ts % 100) as u32;
                        (year, month, day)
                    }
                    TimeType::Month => {
                        let year = (ts / 100) as i32;
                        let month = (ts % 100) as u32;
                        (year, month, 1)
                    }
                    TimeType::Quarter => {
                        let year = (ts / 10) as i32;
                        let quarter = (ts % 10) as u32;
                        let month = match quarter {
                            1 => 1,
                            2 => 4,
                            3 => 7,
                            4 => 10,
                            _ => return Err(anyhow::anyhow!("Invalid quarter value: {}", quarter)),
                        };
                        (year, month, 1)
                    }
                    TimeType::Year => {
                        let year = ts as i32;
                        (year, 1, 1)
                    }
                    TimeType::None => {
                        return Err(anyhow::anyhow!(
                            "Cannot convert timestamp with TimeType::None"
                        ));
                    }
                };
                let date = NaiveDate::from_ymd_opt(year, month, day).ok_or_else(|| {
                    anyhow::anyhow!(
                        "Invalid date components: year={}, month={}, day={}",
                        year,
                        month,
                        day
                    )
                })?;
                let naive = date
                    .and_hms_opt(0, 0, 0)
                    .ok_or_else(|| anyhow::anyhow!("Invalid time components for date: {}", date))?;
                let datetime: DateTime<Utc> = DateTime::from_naive_utc_and_offset(naive, Utc);
                Ok(datetime)
            } else {
                return Err(anyhow::anyhow!(
                    "Invalid timestamp value: expected i64 number"
                ));
            }
        }
        Value::String(s) => {
            let timestamp = DateTime::parse_from_rfc3339(&s)
                .map(|dt| dt.with_timezone(&Utc))
                .map_err(|e| anyhow::anyhow!("Failed to parse timestamp: {}", e))?;
            Ok(timestamp)
        }
        _ => {
            return Err(anyhow::anyhow!(
                "Invalid timestamp value: expected string or number"
            ))
        }
    }
}
fn to_timestamp_vec(time_type: TimeType, values: Vec<Value>) -> anyhow::Result<Vec<DateTime<Utc>>> {
    let mut timestamps: Vec<DateTime<Utc>> = Vec::new();
    for val in values.into_iter() {
        let ts = to_timestamp(time_type, val)?;
        timestamps.push(ts);
    }
    Ok(timestamps)
}
fn to_value_vec(values: Vec<Value>) -> anyhow::Result<Vec<f64>> {
    let mut vals: Vec<f64> = Vec::new();
    for val in values.into_iter() {
        match val {
            Value::Number(num) => {
                if let Some(f) = num.as_f64() {
                    vals.push(f);
                } else {
                    return Err(anyhow::anyhow!("Invalid value: expected f64 number"));
                }
            }
            Value::String(s) => {
                let f: f64 = s
                    .parse()
                    .map_err(|e| anyhow::anyhow!("Failed to parse value string to f64: {}", e))?;
                vals.push(f);
            }
            _ => return Err(anyhow::anyhow!("Invalid value: expected string or number")),
        }
    }
    Ok(vals)
}
